package SFT;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**TimeElements.java
 * Each object of type TimeElement basically is a map between characters and their coefficients.
 * These are the SFT algorithm's output from the input function given to the central function,
 * during the time period between "from" and "to"
 * @author Baruch Weizman, Oren Gilon, Barak Arkis*/
public class TimeElements implements Serializable{
	private static final long serialVersionUID = 1L;
	private double from;
	private double to;
	private Map<Double, Complex> heavyCoeffs;
	
	/*constructor. the time element is defined between the d'th frame and the e'th frame*/
	public TimeElements(double d, double e) {
		this.from=d;
		this.to=e;
		this.heavyCoeffs=new HashMap<Double, Complex>();
	}

	/*returns the heavy Fourier Coeffs that were found during the time period (from getTo() until getFrom())*/
	public Map<Double, Complex> getHeavyCoeffs() {
		return heavyCoeffs;
	}
	
	public double getTo() {
		return to;
	}

	public double getFrom() {
		return from;
	}

	public double getLength() {
		return to-from+1;
	}

	/*adds a member to the map of heavy coefficients*/
	public void add(Double high, Complex coeff) {
		this.heavyCoeffs.put(high, coeff);
		
	}

	/*prints out the time element*/
	public void print() {
		System.out.println("from: "+from+" to: "+to);
		for (double a : heavyCoeffs.keySet()) {
			System.out.println("("+heavyCoeffs.get(a)+")"+"*e^i"+a);
		}
	}

	/*changes the beginning of the time period*/
	public void setFrom(double car) {
		this.from=car;
		
	}

	/*changes the end of the time period*/
	public void setTo(double cdr) {
		this.to=cdr;
		
	}

	/*creates a time element where the units of periods/frame are changed to periods/second, or hertz*/
	public static TimeElements copyTM(TimeElements tm, long frameRate) {
		TimeElements toRet=new TimeElements(tm.getFrom()*1000/(double)frameRate, tm.getTo()*1000/(double)frameRate);
		for (Double key : tm.getHeavyCoeffs().keySet()) {
			toRet.add(new Double(key*frameRate), tm.getHeavyCoeffs().get(key));
		}
		
		return toRet;
		
	}
	
	/*returns a sorted array of the frequencies sorted by descending amplitude*/
	public Double[] sortByAmplitude()
	{
		return sortMapByRealAmplitude(this.heavyCoeffs);
	}
	
	/*returns a sorted array of the frequencies sorted by descending frequency*/
	public Double[] sortByFrequency()
	{
		return sortMapByFrequencies(this.heavyCoeffs);
	}

	/*sets the time element's heavy coefficients to be those defined by the input map*/
	public void setHeavyCoeffs(Map<Double, Complex> heavyCoeffs2) {
		this.heavyCoeffs=heavyCoeffs2;
		
	}
	
	/*bubblesorts the map's keys in descending frequency*/
	private static Double[] sortMapByFrequencies(Map<Double, Complex> map){
		Set<Double>	set=map.keySet();
		Double[] array=new Double[set.size()];
		array=set.toArray(array);
		bubbleSort(array);
		return array;
	}
	
	/*bubblesorts the map's keys by descending order of the amplitude of Complex attached to them */
	private static Double[] sortMapByRealAmplitude(Map<Double,Complex> map){
		Set<Double>	set=map.keySet();
		Double[] array=new Double[set.size()];
		array=set.toArray(array);
		special_bubbleSort(array, map);
		return array;
		
	}


	/*implementations of bubble-sort*/
	private static void special_bubbleSort(Double[] array, Map<Double, Complex> map) {
		for(int i=0; i<array.length; i++){
			if(special_sorted(array, map))
				break;
			for(int j=0; j<array.length-1; j++){
				if(Complex.Abs(map.get(array[j]))<Complex.Abs(map.get(array[j+1]))){
					Double temp=array[j];
					array[j]=array[j+1];
					array[j+1]=temp;
				}
			}
		}		
	}

	/*utility for bubble-sort*/
	private static boolean special_sorted(Double[] array, Map<Double, Complex> map) {
		for(int i=0; i<array.length-1; i++){
			if(Complex.Abs(map.get(array[i]))<Complex.Abs(map.get(array[i+1])))
				return false;
		}
		return true;
	}

	/*utility for bubble-sort*/
	private static boolean sorted(Double[] array) {
		for(int i=0; i<array.length-1; i++){
			if(array[i].doubleValue()<array[i+1].doubleValue())
				return false;
		}
		return true;
	}
	/*implementation of bubble-sort*/
	private static void bubbleSort(Double[] array) {
		for(int i=0; i<array.length; i++){
			if(sorted(array))
				break;
			for(int j=0; j<array.length-1; j++){
				if(array[j].doubleValue()<array[j+1].doubleValue()){
					Double temp=array[j];
					array[j]=array[j+1];
					array[j+1]=temp;
				}
			}
		}
	}

	
	/*converts a timeElement to the function it represents, using MultChi*/
	public Function convertToFunc() {
		MultChi func= new MultChi(1);
		for (Double freq : this.heavyCoeffs.keySet()) {
			func.add_Chi(freq.doubleValue(), this.heavyCoeffs.get(freq));
		}
		return func;
	}

	/*merges the heavy coefficients of two timeElements*/
	public void merge(TimeElements newTm) {
		for (Double freq : newTm.getHeavyCoeffs().keySet()) {
			this.add(freq, newTm.getHeavyCoeffs().get(freq));
			
		}
		
	}

	/**makes sure the function defined by this tm includes the conjugate of each character, as it is supposed to be a real function*/
	public void addConjugate() {
		Map<Double, Complex> map=new HashMap<Double, Complex>();
		for (Double key : heavyCoeffs.keySet()) {
			if(key.doubleValue()==0.0)
				continue;
			boolean contains=false;
			for (Double comp : heavyCoeffs.keySet()) {
				if(Math.abs(key+comp-1)<=0.00001)
					contains=true;
			}
			if(!contains)
				map.put(1-key, Complex.Conjecture(heavyCoeffs.get(key)));
			
		}
		heavyCoeffs.putAll(map);
		
	}
		
	
	
	
}
